package com.sys.utils;

import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DbUtil5 {

    private Connection conn = null;

    /**
     * 连接数据库，默认自动提交
     * @param driver
     * @param url
     * @param username
     * @param password
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public void connect(String driver,
                        String url,
                        String username,
                        String password) throws ClassNotFoundException, SQLException {

        connect(driver, url, username, password,true);
    }

    /**
     * 连接数据库，可以指定是否自动提交
     * @param driver
     * @param url
     * @param username
     * @param password
     * @param autoCommit
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public void connect(String driver,
                        String url,
                        String username,
                        String password,boolean autoCommit) throws ClassNotFoundException, SQLException {

        // 1。加载驱动，类比加载地图、读取地图
        try {
            Class.forName(driver);
            //2。连接数据库
            conn= DriverManager.getConnection(url,username,password);
            // 指定是否自动提交
            conn.setAutoCommit(autoCommit);
        } catch (ClassNotFoundException e) {
            throw e;
        } catch (SQLException e) {
            throw e;
        }
    }

    /**
     * insert into emp (empno,ename,job) values (?,?,?)
     * Object[]{1,'李雷','开发工程师'}
     * @param sql
     * @param params
     * @return
     * @throws ClassNotFoundException
     * @throws DbUtilExcepiton
     */
    public int update(
            String sql,
            Object[] params // 与 ? 相对应的参数，多个，参数可能是字符串、整型、浮点型，所以用的是Object
    ) throws DbUtilExcepiton {

        PreparedStatement prStat = null;
        try {
            //3。创建 Statement、PreparedStatement
            prStat = conn.prepareStatement(sql); // 预编译sql，所以创建该对象时就传入sql

            // 将sql中的 ? 与参数进行绑定
            if(params != null) {
                for (int i = 0; i < params.length; i++) {
                    // i + 1是因为从1开始
                    prStat.setObject(i + 1, params[i]);
                }
            }

            // 4. 执行增删改操作
            int i = prStat.executeUpdate();
            return i;
        } catch (SQLException e) {
            throw new DbUtilExcepiton(e);
        } finally {

            if(prStat!=null){
                try {
                    prStat.close();
                } catch (SQLException e) {
                    throw new DbUtilExcepiton(e);
                }
            }

        }
    }

    public void close() throws SQLException {
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                throw e;
            }
        }
    }
    public void commit() throws SQLException {
        // 通过连接调用 提交事务，数据保存到数据库中
        conn.commit();
    }
    public void rollback() throws SQLException {
        // 通过连接调用回滚事务，让数据恢复到初始状态
        conn.rollback();
    }





/**
 * 获取setter方法
 * @param bean
 * @return 返回Map，属性名作为key，setter方法作为value
 */
private Map<String,Method> getSetterMethod(Class bean) throws IntrospectionException {

    // 将属性 和 相关的set方法，放入到map中
    Map<String,Method> propertyAndSetterMap = new HashMap();
    Method[] methods = bean.getMethods();
    for (Method method : methods) {
        String methodName = method.getName();
        // 如果方法以set开头，就认为是set方法
        if (methodName.startsWith("set")) {
            // 如果方法的参数数量不是1，就说明不是set方法
            if(method.getParameterCount()!=1){
                continue;
            }
            // 截取set后面的部分，作为成员变量名，也是列名
            String field = methodName.substring(3).toLowerCase();
            propertyAndSetterMap.put(field,method);
        }
    }


    return propertyAndSetterMap;
}
/**
 * 指定封装类，将查询的结果封装到该类中
 * 注意：列名要与类成员变量名相同，才能封装
 *
 * @param sql
 * @param clazz 指定封装类
 * @return
 * @throws SQLException
 * @throws InstantiationException
 * @throws IllegalAccessException
 * @throws IllegalArgumentException
 * @throws InvocationTargetException
 */
public <T> List<T> queryBean(String sql,Class<T> clazz,Object ... objects) throws SQLException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException {
    List retList = new ArrayList<>();
    Map<String, Method> propertyAndSetterMap = getSetterMethod(clazz);
    PreparedStatement pstm = null;
    ResultSet rs = null;
    try {
        pstm = conn.prepareStatement(sql);
        if (objects != null && objects.length > 0) {
            for (int i = 0; i < objects.length; i++) {
                pstm.setObject(i + 1, objects[i]);
            }
        }
        rs = pstm.executeQuery();
        ResultSetMetaData rsmd = rs.getMetaData();
        // 返回查询结果列的总数
        int columnNum = rsmd.getColumnCount();
        while (rs.next()) {
            // 通过反射方式实例化
            Object beanObj = clazz.newInstance();
            // 将查询列的值，放入到 javabean（模型类）的 属性中
            // ****难点：需要知道列名，还需要指定属性名
            // 即：原来写的下面这行代码
//            user.setUserId(resultSet.getInt("user_id"));
            for (int i = 1; i <= columnNum; i++) {
                // 取出查询的列名
                String columnLabel = rsmd.getColumnLabel(i).toLowerCase();
                // 取出与该列名相应的值
                Object valueDB = rs.getObject(i);
                // 根据列名，从map中取出与列名相同的属性的set方法
                Method setMethod = propertyAndSetterMap.get(columnLabel);
                // 如果方法为null，表示没有对应的方法，就不再往下执行
                if (setMethod == null) {
                    continue;
                }
                // set方法一定有一个参数
                Class paraType = setMethod.getParameterTypes()[0];
                Object dataTemp = null;
                if (valueDB instanceof BigDecimal) { // mysql数据库的 decimal 类型，jdbc查询出来的类型是 BigDecimal，需要做额外处理
                    BigDecimal temp = (BigDecimal) valueDB;
                    if (paraType == double.class) {
                        dataTemp = temp.doubleValue();
                    } else if (paraType == float.class) {
                        dataTemp = temp.floatValue();
                    }
                } else {
                    dataTemp = valueDB;
                }
                // 调用 set 方法，传值
                setMethod.invoke(beanObj, dataTemp);
            }
            retList.add(beanObj);
        }
        return retList;
    }finally{
        //查询后要释放资源
        if(rs!=null) {
            rs.close();
        }
        if(pstm!=null) {
            pstm.close();
        }
    }

}
}
